/*
 *   Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License").
 *   You may not use this file except in compliance with the License.
 *   A copy of the License is located at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   or in the "license" file accompanying this file. This file is distributed
 *   on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 *   express or implied. See the License for the specific language governing
 *   permissions and limitations under the License.
 */

package com.amazon.opendistroforelasticsearch.sql.legacy.spatial;

import com.amazon.opendistroforelasticsearch.sql.legacy.utils.StringUtils;
import com.google.common.base.Joiner;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Eliran on 4/8/2015.
 */
public class WktToGeoJsonConverter {
    public static String toGeoJson(String wkt) {
        wkt = wkt.toLowerCase();
        int startOfCoordinates = wkt.indexOf("(");
        if (startOfCoordinates == -1) {
            throw new IllegalArgumentException(
                    StringUtils.format("Failed to convert well-known-text [%s] to geometry type", wkt));
        }

        String wktType = wkt.substring(0, startOfCoordinates).trim();
        wkt = wkt.substring(startOfCoordinates);

        String type = "";
        String coordinates = "";
        switch (wktType) {
            case ("point"):
                type = "Point";
                coordinates = pointCoordinatesFromWkt(wkt);
                break;
            case ("polygon"):
                type = "Polygon";
                coordinates = polygonCoordinatesFromWkt(wkt);
                break;
            case ("linestring"):
                type = "LineString";
                coordinates = lineStringCoordinatesFromWkt(wkt);
                break;
            case ("multipolygon"):
                type = "MultiPolygon";
                coordinates = multiPolygonCoordinatesFromWkt(wkt);
                break;
            case ("multipoint"):
                type = "MultiPoint";
                coordinates = multiPointCoordinatesFromWkt(wkt);
                break;
            case ("multilinestring"):
                type = "MultiLineString";
                coordinates = multiLineStringCoordinatesFromWkt(wkt);
                break;
            default:
                throw new IllegalArgumentException("Unsupported well-known-text type: " + wktType);

        }

        return buildGeoJson(type, coordinates);
    }

    //input: ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))
    private static String multiLineStringCoordinatesFromWkt(String wkt) {
        wkt = removeBrackets(wkt, 1);
        String lineStringsWithPipeSeparator = wkt.replaceAll("\\s*\\)\\s*,\\s*\\(", ")|(");
        String[] lineStrings = lineStringsWithPipeSeparator.split("\\|");
        String[] coordinates = new String[lineStrings.length];
        for (int i = 0; i < lineStrings.length; i++) {
            coordinates[i] = lineStringCoordinatesFromWkt(lineStrings[i]);
        }
        String multiLineStringCoordinates = Joiner.on(",").join(coordinates);
        return String.format("[%s]", multiLineStringCoordinates);

    }

    //input v1:MULTIPOINT (10 40, 40 30, 20 20, 30 10)
    //v2:MULTIPOINT ((10 40), (40 30), (20 20), (30 10))
    private static String multiPointCoordinatesFromWkt(String wkt) {
        wkt = removeBrackets(wkt, 1);
        boolean isSecondVersionMultiPoint = wkt.contains("(");
        String coordinates = "";
        if (isSecondVersionMultiPoint) {
            //(10 40), (40 30), (20 20)-> 10 40, 40 30, 20 20
            wkt = wkt.replaceAll("\\(|\\)", "");
        }
        coordinates = getJsonArrayFromListOfPoints(wkt);
        return coordinates;
    }

    //input (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))
    private static String multiPolygonCoordinatesFromWkt(String wkt) {
        wkt = removeBrackets(wkt, 1);
        String polygonsWithPipeSeparator = wkt.replaceAll("\\s*\\)\\s*\\)\\s*,\\s*\\(\\s*\\(\\s*", "))|((");
        String[] polygons = polygonsWithPipeSeparator.split("\\|");
        String[] polygonsCoordinates = new String[polygons.length];
        for (int i = 0; i < polygons.length; i++) {
            polygonsCoordinates[i] = polygonCoordinatesFromWkt(polygons[i]);
        }
        String coordinates = Joiner.on(",").join(polygonsCoordinates);
        return String.format("[%s]", coordinates);
    }

    //input : (30 10, 10 30, 40 40)
    private static String lineStringCoordinatesFromWkt(String wkt) {
        wkt = removeBrackets(wkt, 1);
        return getJsonArrayFromListOfPoints(wkt);
    }

    //input: v1:((35 10, 45 45, 15 40, 10 20, 35 10))
    //v2:((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))
    private static String polygonCoordinatesFromWkt(String wkt) {
        wkt = removeBrackets(wkt, 2);
        String coordinates;
        boolean polygonContainsInnerHoles = wkt.contains("(");
        if (polygonContainsInnerHoles) {
            String[] polygons = wkt.split("\\s*\\)\\s*,\\s*\\(\\s*");
            String[] coordinatesOfPolygons = new String[polygons.length];
            for (int i = 0; i < polygons.length; i++) {
                String polygonCoordinates = getJsonArrayFromListOfPoints(polygons[i]);
                coordinatesOfPolygons[i] = polygonCoordinates;
            }
            coordinates = Joiner.on(",").join(coordinatesOfPolygons);
        } else {
            coordinates = getJsonArrayFromListOfPoints(wkt);
        }
        return String.format("[%s]", coordinates);
    }

    private static String getJsonArrayFromListOfPoints(String pointsInWkt) {
        String[] points = pointsInWkt.split(",");
        List<String> coordinates = new ArrayList<>();
        for (String point : points) {
            coordinates.add(extractCoordinateFromPoint(point));
        }

        String joinedCoordinates = Joiner.on(",").join(coordinates);
        return String.format("[%s]", joinedCoordinates);
    }

    private static String buildGeoJson(String type, String coordinates) {
        return String.format("{\"type\":\"%s\", \"coordinates\": %s}", type, coordinates);
    }

    //input : (30 10)
    public static String pointCoordinatesFromWkt(String wkt) {
        wkt = removeBrackets(wkt, 1);
        return extractCoordinateFromPoint(wkt);
    }

    private static String extractCoordinateFromPoint(String point) {
        String pointPattern = "(\\s*)([0-9\\.-]+)(\\s*)([0-9\\.-]+)(\\s*)";
        return point.replaceAll(pointPattern, "[$2,$4]");
    }

    private static String removeBrackets(String wkt, int num) {
        String result = wkt;
        for (int i = 0; i < num; i++) {
            int lastClosingBrackets = result.lastIndexOf(")");
            int firstOpenBrackets = result.indexOf("(");
            if (lastClosingBrackets == -1 || firstOpenBrackets == -1) {
                throw new IllegalArgumentException("Illegal syntax: " + wkt);
            }
            result = result.substring(firstOpenBrackets + 1, lastClosingBrackets);
        }
        return result;
    }

}
